package com.mocchira.dq2011.sp;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import com.mocchira.dq2011.sp.SlidePuzzleAnalyzer.Result.Direction;

public class SlidePuzzleAnalyzeExecutor {
    private static ThreadPoolExecutor executor;
    private static SlidePuzzleAnalyzer.Result[] result;
    private static SlidePuzzleInputParser.Input input;
    private static ArrayBlockingQueue<Runnable> queue;
    public static void execute(SlidePuzzleInputParser.Input input, File answer, final File dst, final int square, final int timeout, final int cc) {
        if (!dst.canWrite())
            return;
        SlidePuzzleAnalyzeExecutor.input = input;
        queue = new ArrayBlockingQueue<Runnable>(input.header.total);
        result = SlidePuzzleAnswerReader.getResult(answer);
        int mainThreadNum = cc == 1 ? 1: 8;
        executor = new ThreadPoolExecutor(mainThreadNum, mainThreadNum, 5, TimeUnit.SECONDS, queue);
        int i = 0;
        for (final SlidePuzzleInputParser.Puzzle puzzle: input.listPuzzle) {
            final int idx = i;
            if (result[idx].toString().length() == 0) {
                executor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("execute start idx:" + idx);
                    if (puzzle.width * puzzle.height >= square) {
                        System.out.println("skip end idx:" + idx);
                        return;
                    }
                    if (cc == 1) {
                        final SlidePuzzleAnalyzer.Result[] lresult = new SlidePuzzleAnalyzer.Result[64];
                        ArrayBlockingQueue<Runnable> lqueue = new ArrayBlockingQueue<Runnable>(64);;
                        ThreadPoolExecutor lexecutor = new ThreadPoolExecutor(64, 64, 5, TimeUnit.SECONDS, lqueue);
                        @SuppressWarnings("serial")
                        List<SlidePuzzleAnalyzer.Result.Direction> dirs = new ArrayList<SlidePuzzleAnalyzer.Result.Direction>(){
                            {add(Direction.D);
                             add(Direction.U);
                             add(Direction.L);
                             add(Direction.R);
                            }};
                        final Map<String, SlidePuzzleAnalyzer> analyzers = new HashMap<String, SlidePuzzleAnalyzer>();
                        int li = 0;
                        for (final Direction dir1: dirs) {
                            for (final Direction dir2: dirs) {
                                for (final Direction dir3: dirs) {
                                    lresult[li] = new SlidePuzzleAnalyzer.Result();
                                    final int fli = li;
                                    lexecutor.execute(new Runnable() {
                                        @Override
                                        public void run() {
                                            System.out.println("execute stard idx-lid:" + idx + "-" + fli);
                                            @SuppressWarnings("serial")
                                            SlidePuzzleAnalyzer analyzer = new SlidePuzzleAnalyzer(puzzle, new ArrayList<SlidePuzzleAnalyzer.Result.Direction>(){{add(dir1);add(dir2);add(dir3);}}, lresult[fli], timeout);
                                            //System.out.println(analyzer.toString());
                                            analyzer.execute();
                                            analyzers.put(dir1.name() + dir2.name() + dir3.name(), analyzer);
                                            //System.out.println(analyzer.toString());
                                            System.out.println("execute end idx-lid:" + idx + "-" + fli);
                                        }
                                    });
                                    li++;
                                }
                            }
                        }
                        lexecutor.shutdown();
                        while(!lexecutor.isTerminated()) {
                            boolean done = false;
                            try {
                                lexecutor.awaitTermination(3, TimeUnit.SECONDS);
                            } catch (InterruptedException ex) {
                                ;
                            }
                            for (int i = 0; i < lresult.length; i++) {
                                if (lresult[i].toString().length() > 0) {
                                    result[idx] = lresult[i];
                                    done = true;
                                    break;
                                }
                            }
                            if (done)
                                break;
                        }
                        for (String key: analyzers.keySet()) {
                            SlidePuzzleAnalyzer analyzer = analyzers.get(key);
                            analyzer.reqStop = true;
                        }
                        while(!lexecutor.isTerminated()) {
                            try {
                                lexecutor.awaitTermination(3, TimeUnit.SECONDS);
                            } catch (InterruptedException ex) {
                                ;
                            }
                        }
                    } else {
                        SlidePuzzleAnalyzer analyzer = new SlidePuzzleAnalyzer(puzzle, new ArrayList<SlidePuzzleAnalyzer.Result.Direction>(), result[idx], timeout);
                        //System.out.println(analyzer.toString());
                        analyzer.execute();
                        //System.out.println(analyzer.toString());
                    }
                    synchronized (SlidePuzzleAnalyzeExecutor.input) {
                        SlidePuzzleAnalyzeExecutor.input.header.lx -= result[idx].getLNum();
                        SlidePuzzleAnalyzeExecutor.input.header.rx -= result[idx].getRNum();
                        SlidePuzzleAnalyzeExecutor.input.header.ux -= result[idx].getUNum();
                        SlidePuzzleAnalyzeExecutor.input.header.dx -= result[idx].getDNum();
                        if (SlidePuzzleAnalyzeExecutor.input.header.lx < 0 ||
                            SlidePuzzleAnalyzeExecutor.input.header.rx < 0 ||
                            SlidePuzzleAnalyzeExecutor.input.header.ux < 0 ||
                            SlidePuzzleAnalyzeExecutor.input.header.dx < 0)
                            result[idx] = null;
                        System.out.println("execute end idx:" + idx);
                        System.out.println("header:" + SlidePuzzleAnalyzeExecutor.input.header);
                        write(dst);
                    }
                    System.out.println("execute end idx:" + idx + " result:" + result[idx]);
                }
                });
            } else {
                synchronized (SlidePuzzleAnalyzeExecutor.input) {
                    SlidePuzzleAnalyzeExecutor.input.header.lx -= result[idx].getLNum();
                    SlidePuzzleAnalyzeExecutor.input.header.rx -= result[idx].getRNum();
                    SlidePuzzleAnalyzeExecutor.input.header.ux -= result[idx].getUNum();
                    SlidePuzzleAnalyzeExecutor.input.header.dx -= result[idx].getDNum();
                }
                System.out.println("skip idx:" + idx + " already done.");
            }
            i++;
            // @test
            //break;
        }
        executor.shutdown();
        while(!executor.isTerminated()) {
            try {
                executor.awaitTermination(3, TimeUnit.SECONDS);
            } catch (InterruptedException ex) {
                ;
            }
        }
        write(dst);
    }
    private static void write(File dst) {
        FileWriter writer = null;
        try {
            writer = new FileWriter(dst);
            for (int j = 0; j < result.length; j++) {
                if (result[j] != null) {
                    writer.write(result[j].toString());
                }
                writer.write(System.getProperty("line.separator"));
            }
        } catch (IOException ex) {
                System.out.println(ex.getMessage());
        } finally {
            try {
                if (writer != null) writer.close();
            } catch (Exception ex) {;}
        }
    }
}
